﻿using BrickCreator;
using Ionic.Zip;
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;

namespace BCv29
{
	//To prevent Visual Studio from thinking this is a form file.
	[System.ComponentModel.DesignerCategory("")]
	internal static class Program
	{
		/// <summary>
		/// The main entry point for the application.
		/// </summary>
		[STAThread]
		private static void Main()
		{
			Application.EnableVisualStyles();
			Application.SetCompatibleTextRenderingDefault(false);
			Application.Run(new BrickCreatorForm());
		}
	}

	//Thank god that you can put the same class like this in 2 different files.
	public partial class BrickCreatorForm : Form
	{
		#region Properties

		//down = is the mouse button down?
		//ins = is the mouse cursor inside the list box?
		private bool down = false, ins = false;

		private int index = 0;

		//These three booleans are VERY important. I cannot stress enough their importance.
		//The isEditing boolean is what holds together the editing system, without it, you wouldn't be able to edit bricks.
		//The isUpdating bool is ESPECIALLY important. It lets the program know when a change in some field is because of user input,
		//or because of the script itself doing updates to the display. You cannot make this kind of program without one of these.
		//isCheckEdit is a little thing that I use to check if an edit is initiated by checking the check box,
		//this is VERY IMPORTANT for use with those.
		private bool isEditing = false, isUpdating = false, isCheckEdit = false;

		private Brick nextBrick = new Brick(1, 1, 1);

		//This is just an easier way to control which buttons are enabled when.
		private bool[,] ButtonBools = new bool[6, 3] {{true, true, true}, {false, true, false}, {false, false, false},
                                                     {true, false, true}, {false, false, true}, {false, false, false}};

		//index 0: nothing selected
		//index 1: something selected, not editing
		//index 2: something selected, editing
		private bool[] thirdbools = new bool[3] { false, true, true };

		private string[] thirdstrs = new string[3] { "Trash All", "Trash Brick", "Save Brick" };
		private string[] firststrs = new string[3] { "New Brick", "New Brick", "Cancel" };

		private brickManager mg = new brickManager();
		private Brick temp;
		private Brick temp2;

		#endregion Properties

		#region Methods

		#region Brick-Related Methods

		private void createNewBrick()
		{
			//Add the next unused brick
			mg.addBrick(nextBrick);

			//Assign the bricks dimensions to variables for easiness
			byte w = nextBrick.Width,
				 l = nextBrick.Length,
				 h = nextBrick.Height;

			//Add the item to the list in the format Width x Lenth x Height (in studs)
			brickListBox.Items.Add(w + "x" + l + "x" + (Math.Floor(10 * h / 3f) / 10).ToString());

			//Finds the next brick to be created
			findNextBrick();

			brickListBox.SelectedIndex = brickListBox.Items.Count - 1;
			index = brickListBox.SelectedIndex;
			brickListBox_indexChanged(new Object(), new EventArgs());

			//The reason that there isn't any global list update function is that it would add unnessescary complexity.
			//Instead of updating all the boxes all the time, why not just add things here and there on the fly? On the fly is good.
		}

		//This function finds the smallest unused brick. (Usually.)
		private bool findNextBrick()
		{
			//Assign the bricks dimensions to variables for easiness
			byte w = nextBrick.Width,
				 l = nextBrick.Length,
				 h = nextBrick.Height;

			//This loop here gets the next unused brick, even if it has to loop through every possible one.
			//Believe it or not, this is the most efficiant way of doing it.
			bool done = false;

			//I keep Height and Width the same at the start because those are the last two to change in these checks.
			for (h = h; h <= 255 && !done; h++)
			{
				for (w = w; w <= 64 && !done; w++)
				{
					for (l = (byte)(l + 1); l <= 64 && !done; l++)
						if (!mg.brickExists(l, w, h))
							done = true;
					if (!done)
						l = 0;
				}
				if (!done)
					w = 1;
			}

			//I decrement these because for some reason they always go up by one when the loop ends.
			l--; w--; h--;

			//Set the next brick to the new dimensions
			nextBrick = new Brick(w, l, h);

			//This should always return true. Always. Always always always.
			return done;
		}

		#endregion Brick-Related Methods

		#region Editing-Related Methods

		//This function is called when a unedited brick is selected, then something about it is changed.
		private void startEdit()
		{
			//Set the isEditing bool to true
			isEditing = true;

			Brick curr = mg.Bricks[index];
			//Set the temp brick to the currently selected one
			temp = new Brick((byte)widthCounter.Value, (byte)lengthCounter.Value, (byte)heightCounter.Value);

			//This part here checks to see if the brick we're starting to edit has any custom fields inputted,
			//and if so, transfers those onto the edited brick.
			bool ac = curr.AutoCategory, asc = curr.AutoSubCategory, aun = curr.AutoUiName;

			temp.AutoCategory = ac;
			temp.AutoSubCategory = asc;
			temp.AutoUiName = aun;

			if (!ac)
				temp.Category = curr.Category;
			if (!asc)
				temp.subCategory = curr.subCategory;
			if (!aun)
				temp.UiName = curr.UiName;
			
			temp2 = curr;

			//Update the buttons
			TabsControl_indexChanged(new object(), new EventArgs());

			if (!(checkBox3.Checked || checkBox2.Checked || checkBox1.Checked))
				brickListBox_indexChanged(new object(), new EventArgs());
		}

		//This function merely stops the editing process, and sets the edited brick to be displayed with designated dimensions.
		private void stopEdit(Brick disp)
		{
			//reset the temporary brick
			temp = new Brick();

			//Reset the second temporary brick (the original)
			temp2 = new Brick();

			//Set isEditing to false (for obvious reasons)
			isEditing = false;

			//Update the buttons/boxes
			TabsControl_indexChanged(new object(), new EventArgs());
			brickListBox_indexChanged(new object(), new EventArgs());

			//Then set the name in the list.
			string asdf = disp.Width + "x" + disp.Length + "x" + (Math.Floor(10 * disp.Height / 3f) / 10).ToString();
			brickListBox.Items[index] = asdf;
		}

		//This function is called to update things like the display and boxes while editing a brick.
		private void updateTemp()
		{
			byte a = (byte)widthCounter.Value, b = (byte)lengthCounter.Value, c = (byte)heightCounter.Value;

			//Add a star to the name and use the counters info instead.
			isUpdating = true;
			brickListBox.Items[index] = a + "x" + b + "x" + (Math.Floor(10 * c / (decimal)3) / 10).ToString() + "*";
			isUpdating = false;

			temp.Width = a;
			temp.Length = b;
			temp.Height = c;

			if (temp.AutoCategory)
				categoryBox.Text = temp.Category;
			if (temp.AutoSubCategory)
				subCatBox.Text = temp.subCategory;
			if (temp.AutoUiName)
				UiBox.Text = temp.UiName;

			//We can't save the brick if it already exists, that will create an error.
			//However if it's the same as the original, that's okay.
			thirdButton.Enabled = !mg.brickExists(temp.Length, temp.Width, temp.Height) || (temp2.Width == a && temp2.Length == b && temp2.Height == c);
		}

		//This one is just for updating when a checkbox is changed.
		//I could do this with fewer lines of code, but I want this program to be as fast as possible.
		private void updateTemp(byte check)
		{
			switch (check)
			{
				case 0:
					bool a = checkBox1.Checked;

					categoryBox.ReadOnly = !a;
					categoryBox.BackColor = a ? Color.White : Color.Gainsboro;
					temp.AutoCategory = !a;
					categoryBox.Text = temp.Category;
					break;

				case 1:
					bool b = checkBox2.Checked;

					subCatBox.ReadOnly = !b;
					subCatBox.BackColor = b ? Color.White : Color.Gainsboro;
					temp.AutoSubCategory = !b;
					subCatBox.Text = temp.subCategory;
					break;

				case 2:
					bool c = checkBox3.Checked;

					UiBox.ReadOnly = !c;
					UiBox.BackColor = c ? Color.White : Color.Gainsboro;
					temp.AutoUiName = !c;
					UiBox.Text = temp.UiName;
					break;

				default:
					throw new ArgumentOutOfRangeException("check", "Value must be < 3!");
			}

			updateTemp();
		}

		#endregion Editing-Related Methods

		#region Packing-Related Methods

		private void packageBricks()
		{
			//Construct all the files that need zipping
			byte[][] blbs = makeBLBs(mg);
			byte[] csfile = makeCS(mg);
			byte[] note = makeNotice();
			byte[] desc = makeDescription();

			makeZip(blbs, csfile, note, desc);
		}

		//This method here creates a jagged (array of arrays) byte array for writing to a zip file.
		//I was originally planning on using a class called MemoryStream for this,
		//because it quite simply is a stream stored in memory. Seems perfect for my needs! (read: http://hu.je/8r) But no.
		//I had issues with them, and it really is easier just to have a byte array instead of switching mediums.
		//It would just be an unnnessescary step!
		private byte[][] makeBLBs(brickManager Manager)
		{
			//the ASCIIEncoding class lets me get the byte values from text in the ASCII encoding scheme.
			//I don't use UTFEncoding because these files really don't need any fhwacky symbols.
			ASCIIEncoding enc = new ASCIIEncoding();
			byte[][] bytes = new byte[Manager.Bricks.Count][];
			int current = -1;

			foreach (Brick br in Manager.Bricks)
				bytes[++current] = enc.GetBytes(br.Width + " " + br.Length + " " + br.Height + "\nBRICK");

			return bytes;
		}

		private byte[] makeCS(brickManager Manager)
		{
			ASCIIEncoding enc = new ASCIIEncoding();
			List<byte> bytes = new List<byte>();

			bytes.AddRange(enc.GetBytes("//Created with Ipquarx's Brick Creator v2.9\r\n"));
			bytes.AddRange(enc.GetBytes("//For personal use ONLY. Not for uploading."));

			foreach (Brick br in Manager.Bricks)
			{
				bytes.AddRange(enc.GetBytes("\r\n\r\ndatablock fxDTSBrickData(brick" + br.Width + "x" + br.Length + "x" + br.Height + "Data)\r\n"));
				bytes.AddRange(enc.GetBytes("{\r\n\tbrickFile = \"./" + br.fileName + ".blb\";"));
				bytes.AddRange(enc.GetBytes("\r\n\tcategory = \"" + br.Category + "\";"));
				bytes.AddRange(enc.GetBytes("\r\n\tsubCategory = \"" + br.subCategory + "\";"));
				bytes.AddRange(enc.GetBytes("\r\n\tuiName = \"" + br.UiName + "\";"));
				bytes.AddRange(enc.GetBytes("\r\n};"));
			}

			return bytes.ToArray();
		}

		//This is to hopefully prevent billions of crappy brickpacks from flooding add-on hosting sites.
		//This can't prevent people from just removing the file, but people that are smart enough to do that
		//hopefully won't create tons of crappy brickpacks and try to upload them all.
		private byte[] makeNotice()
		{
			ASCIIEncoding enc = new ASCIIEncoding();
			List<byte> bytes = new List<byte>();

			//Now, I know what you're thinking: "If this message is the same every time, then why are you doing it like this?"
			//And I thought that too at first. But then I tried every possible non-intensly-tedious way
			//of making it a set array/string with no methods having to be called, and they all gloriously fucked up.
			//So it's staying this way until further notice.
			bytes.AddRange(enc.GetBytes("If you see this file, and it has been uploaded to any public addon hosting website,\r\n"));
			bytes.AddRange(enc.GetBytes("please flag it for deletion. This was created with"));
			bytes.AddRange(enc.GetBytes("Ipquarx's Brick Creator v2.9, and is for personal use only.\r\n"));
			bytes.AddRange(enc.GetBytes("And I as the creator of the software that made this notice file,\r\n"));
			bytes.AddRange(enc.GetBytes("phrohibit the public uploading of any file(s) created with my program without my written permission."));

			return bytes.ToArray();
		}

		private byte[] makeDescription()
		{
			ASCIIEncoding enc = new ASCIIEncoding();

			//I don't need to transfer from a list here because the description format is REALLY simple.
			return enc.GetBytes("Title: " + titleBox.Text + "\r\nAuthor: " + nameBox.Text + "\r\n" + descriptionBox.Text);
		}

		private void makeZip(byte[][] blbs, byte[] csfile, byte[] note, byte[] desc)
		{
			//In case I ever want to change these.
			string csname = "Server.cs";
			string notename = "Notice.txt";
			string descname = "Description.txt";

			using (ZipFile zipped = new ZipFile())
			{
				//Add the BLBs
				for (int a = 0; a < blbs.Length; a++)
					zipped.AddEntry(mg.Bricks[a].fileName + ".blb", blbs[a]);

				//Add the cs file, description, and notice.
				zipped.AddEntry(csname, csfile);
				zipped.AddEntry(descname, desc);
				zipped.AddEntry(notename, note);

				//Save the zip
				zipped.Save("Brick_" + titleBox.Text + ".zip");
			}
		}

		#endregion Packing-Related Methods

		#endregion Methods

		#region Events

		private void BrickCreatorForm_Load(object sender, EventArgs e)
		{
			//We don't have to do a ton here really.

			//Create a new brick to start off the pack
			createNewBrick();

			//Set the selected index to that brick, and call the event
			brickListBox.SelectedIndex = 0;
			brickListBox_indexChanged(sender, e);

			nameBox.Text = Properties.Settings.Default.Name;
		}

		#region Button Events

		private void firstButton_Click(object sender, System.EventArgs e)
		{
			//If this is true, then the text says "New Brick"
			if (!isEditing)
				createNewBrick();
			else //Otherwise, the text says "Cancel", meaning to cancel editing the currently selected brick.
				stopEdit(temp2);
		}

		private void packBrickButton_Click(object sender, System.EventArgs e)
		{
			packageBricks();
		}

		private void thirdButton_Click(object sender, System.EventArgs e)
		{
			if (!isEditing)
			{
				if (index < 0)
				{
					//Reset the brick manager, clear the list, reset the next brick, and redo the form loading.
					mg = new brickManager();
					nextBrick = new Brick(1, 1, 1);
					brickListBox.Items.Clear();
					BrickCreatorForm_Load(sender, e);
				}
				else
				{
					//Reset the temporary bricks
					temp = new Brick();
					temp2 = new Brick();

					//Remove the brick from the brick manager and the list
					mg.removeBrick(mg.Bricks[index]);
					brickListBox.Items.RemoveAt(index);

					int count = brickListBox.Items.Count;

					//then set the new index if it needs changing.
					if (index >= count)
						index = count - 1;

					brickListBox.SelectedIndex = index;

					brickListBox_indexChanged(sender, e);
				}
			}
			else
			{
				//Make sure if there is any custom text added, to include that in the brick.
				if (!temp.AutoCategory)
					temp.Category = categoryBox.Text;
				if (!temp.AutoSubCategory)
					temp.subCategory = subCatBox.Text;
				if (!temp.AutoUiName)
					temp.UiName = UiBox.Text;

				//Set the brick in the manager to match the current temporary brick
				mg.Bricks[index] = temp;

				//Then stop editing.
				stopEdit(temp);
			}
		}

		#endregion Button Events

		#region Index Events

		//This function is the backbone of the entire display scheme,
		//it updates all the boxes, it switches the buttons,
		private void brickListBox_indexChanged(object sender, EventArgs e)
		{
			if (isUpdating)
				return;

			//Not much to see here.
			if (brickListBox.SelectedIndex >= 0)
			{
				if (!isEditing)
				{
					index = brickListBox.SelectedIndex;
					Brick current = mg.Bricks[index];

					isUpdating = true;
					widthCounter.Value = current.Width;
					lengthCounter.Value = current.Length;
					heightCounter.Value = current.Height;

					categoryBox.Text = current.Category;
					subCatBox.Text = current.subCategory;
					UiBox.Text = current.UiName;

					categoryBox.ReadOnly = current.AutoCategory;
					subCatBox.ReadOnly = current.AutoSubCategory;
					UiBox.ReadOnly = current.AutoUiName;

					categoryBox.BackColor = current.AutoCategory ? Color.Gainsboro : Color.White;
					subCatBox.BackColor = current.AutoSubCategory ? Color.Gainsboro : Color.White;
					UiBox.BackColor = current.AutoUiName ? Color.Gainsboro : Color.White;

					//This is just for convienence.
					if (TabsControl.SelectedIndex != 0)
						TabsControl.SelectedIndex = 0;

					checkBox3.Enabled = true;
					checkBox2.Enabled = true;
					checkBox1.Enabled = true;
					checkBox3.Checked = !current.AutoUiName;
					checkBox2.Checked = !current.AutoSubCategory;
					checkBox1.Checked = !current.AutoCategory;

					isUpdating = false;

					widthCounter.Enabled = true;
					lengthCounter.Enabled = true;
					heightCounter.Enabled = true;

					thirdButton.Text = thirdstrs[1];
					firstButton.Text = firststrs[1];

					thirdButton.Enabled = brickListBox.Items.Count != 1;
				}
				else
				{
					if (!isCheckEdit)
					{
						checkBox3.Checked = !temp.AutoUiName;
						checkBox2.Checked = !temp.AutoSubCategory;
						checkBox1.Checked = !temp.AutoCategory;
					}

					thirdButton.Text = thirdstrs[2];
					firstButton.Text = firststrs[2];
				}
			}
			else if (brickListBox.SelectedIndex < 0)
			{
				isUpdating = true;
				widthCounter.Value = 1;
				lengthCounter.Value = 1;
				heightCounter.Value = 1;

				widthCounter.Enabled = false;
				lengthCounter.Enabled = false;
				heightCounter.Enabled = false;

				//Visual studio likes me to use resources for any literals, so I complied for the string "---".
				//I saw no difference in performance or file size, so meh, whatever.
				categoryBox.Text = Properties.Resources.a;
				subCatBox.Text = Properties.Resources.a;
				UiBox.Text = Properties.Resources.a;

				//if nothing is selected or we're not editing, the check boxes are disabled.
				checkBox3.Enabled = isEditing;
				checkBox2.Enabled = isEditing;
				checkBox1.Enabled = isEditing;
				checkBox3.Checked = false;
				checkBox2.Checked = false;
				checkBox1.Checked = false;

				isUpdating = false;

				thirdButton.Text = thirdstrs[0];
				firstButton.Text = firststrs[0];
			}
		}

		private void TabsControl_indexChanged(object sender, EventArgs e)
		{
			int index = TabsControl.SelectedIndex;
			if (!isEditing)
			{
				firstButton.Enabled = ButtonBools[index, 0];
				packBrickButton.Enabled = ButtonBools[index, 1];
				thirdButton.Enabled = ButtonBools[index, 2];
			}
			else
			{
				firstButton.Enabled = ButtonBools[index + 3, 0];
				packBrickButton.Enabled = ButtonBools[index + 3, 1];
				thirdButton.Enabled = ButtonBools[index + 3, 2];
			}
		}

		#endregion Index Events

		#region Mouse Events

		//This section here decides how the the whole list box works, in terms of behaviour.
		//It's what makes the entire thing work fluidly.

		private void brickListBox_mouseDown(object sender, MouseEventArgs e)
		{
			down = true;
			brickListBox_mouseMove(sender, e);
		}

		private void brickListBox_mouseEnter(object sender, EventArgs e)
		{
			ins = true;
		}

		private void brickListBox_mouseLeave(object sender, EventArgs e)
		{
			ins = false;
		}

		private void brickListBox_mouseMove(object sender, MouseEventArgs e)
		{
			//If the mouse is inside the box, and the mouse button is down,
			if ((down & ins) && !isEditing)
			{
				int old = index;

				//then set the selected index to the index a the mouse point.
				index = brickListBox.IndexFromPoint(e.Location);
				brickListBox.SelectedIndex = index;

				//and if it's different than it was before, do a indexchanged update.
				//Not doing this every time saves a LOT of cpu time.
				if (old != index)
					brickListBox_indexChanged(sender, EventArgs.Empty);
			}
			else if (isEditing)
			{
				//If we're editing, then we want to lock in the index until it's saved.
				brickListBox.SelectedIndex = index;
			}
		}

		private void brickListBox_mouseUp(object sender, MouseEventArgs e)
		{
			down = false;
		}

		#endregion Mouse Events

		#region Counter Events

		private void widthCounter_ValueChanged(object sender, System.EventArgs e)
		{
			if (isUpdating)
				return;

			if (!isEditing)
				startEdit();

			int val1 = (int)widthCounter.Value;
			int val2 = (int)lengthCounter.Value;

			if (val1 > val2)
			{
				isUpdating = true;
				lengthCounter.Value = val1;
			}

			isUpdating = false;

			updateTemp();
		}

		private void lengthCounter_ValueChanged(object sender, System.EventArgs e)
		{
			if (isUpdating)
				return;

			if (!isEditing)
				startEdit();

			int val1 = (int)lengthCounter.Value;
			int val2 = (int)widthCounter.Value;

			if (val1 < val2)
			{
				isUpdating = true;
				lengthCounter.Value = val2;
			}

			isUpdating = false;
			updateTemp();
			//line 666? What line 666? I don't see a line 666. Nope. None. NOPE
		}

		private void heightCounter_ValueChanged(object sender, System.EventArgs e)
		{
			if (isUpdating)
				return;

			if (!isEditing)
				startEdit();

			updateTemp();
		}

		#endregion Counter Events

		#region Check Box Events

		//Yes, I know the numbers are fucked up.
		//I plan on fixing that later.
		//IT WORKS OKAY STOP JUDGING ME :C

		private void checkBox3_CheckedChanged(object sender, System.EventArgs e)
		{
			if (isUpdating)
				return;

			if (!isEditing)
			{
				startEdit();
				updateTemp(0);

				isCheckEdit = true;
				brickListBox_indexChanged(sender, e);
				isCheckEdit = false;
			}
			else
				updateTemp(0);
		}

		private void checkBox2_CheckedChanged(object sender, System.EventArgs e)
		{
			if (isUpdating)
				return;

			if (!isEditing)
			{
				startEdit();
				updateTemp(1);

				isCheckEdit = true;
				brickListBox_indexChanged(sender, e);
				isCheckEdit = false;
			}
			else
				updateTemp(1);
		}

		private void checkBox1_CheckedChanged(object sender, System.EventArgs e)
		{
			if (isUpdating)
				return;

			if (!isEditing)
			{
				startEdit();
				updateTemp(2);

				isCheckEdit = true;
				brickListBox_indexChanged(sender, e);
				isCheckEdit = false;
			}
			else
				updateTemp(2);
		}

		#endregion Check Box Events

		#region Textbox Events

		private void categoryBox_textChanged(object sender, System.EventArgs e)
		{
			if(!isUpdating && !isEditing)
				startEdit();
		}

		#endregion Textbox Events

		#endregion Events
	}
}